package com.yanqu.road.dao.dbV2;

import com.yanqu.road.dao.dbV2.serializer.Serializer;

import java.lang.reflect.Field;
import java.lang.reflect.Type;
import java.sql.ResultSet;
import java.util.*;

public class Table {

    private String tableName;

    private String insertSql;

    private String updateSql;

    private String createTableSql;

    private String dbUrl;

    private String implClassName;

    private Class cls;
    private Map<String, TableField> fieldMap = new LinkedHashMap<>();

    public Table(Class cls, String dbUrl) {
        if (!cls.isAnnotationPresent(ITable.class)) {
            throw new RuntimeException("未添加ITable注解");
        }

        Class superclass = cls.getSuperclass();

        boolean equals = superclass.equals(BaseEntity.class);

        if (!equals) {
            throw new RuntimeException("未继承BaseEntity");
        }

        this.dbUrl = dbUrl;
        this.cls = cls;
        ITable annotation = (ITable) cls.getAnnotation(ITable.class);
        this.tableName = annotation.tableName();

        String implClassName = String.format("%sDaoImpl", cls.getSimpleName());
        this.implClassName = implClassName;
        Field[] fields = cls.getFields();
        StringBuilder insertBuilder = new StringBuilder();
        StringBuilder insertParamBuilder = new StringBuilder();
        insertBuilder.append(String.format("INSERT INTO `%s`(", this.tableName));
        StringBuilder updateBuilder = new StringBuilder();
        StringBuilder updateWhereBuilder = new StringBuilder();
        updateWhereBuilder.append(" WHERE ");
        updateBuilder.append(String.format("UPDATE `%s` SET ", this.tableName));
        for (Field field : fields) {
            IField iField = field.getAnnotation(IField.class);
            if (iField == null) {
                continue;
            }
            insertBuilder.append(String.format("`%s`,", iField.fieldName()));
            insertParamBuilder.append("?,");

            if (iField.isUpdateKey()) {
                updateWhereBuilder.append(String.format("`%s` = ? AND ", iField.fieldName()));
            } else {
                updateBuilder.append(String.format("`%s` = ?,", iField.fieldName()));
            }

            TableField tableField = TableField.getInstance(iField);
            String types = this.parseTypes(field);
            tableField.setTypes(types);
            String deserialize = this.parseDeserialize(field);
            tableField.setDeSerializer(deserialize);
            String serialize = this.parseSerialize(field);
            tableField.setSerializer(serialize);
            this.fieldMap.put(field.getName(), tableField);
        }
        insertBuilder.deleteCharAt(insertBuilder.length() - 1);
        insertParamBuilder.deleteCharAt(insertParamBuilder.length() - 1);
        insertBuilder.append(String.format(") VALUES (%s)", insertParamBuilder));

        updateBuilder.deleteCharAt(updateBuilder.length() - 1);
        updateWhereBuilder.delete(updateWhereBuilder.length() - 4, updateWhereBuilder.length());
        updateBuilder.append(updateWhereBuilder);

        this.insertSql = insertBuilder.toString();
        this.updateSql = updateBuilder.toString();

        this.generateCreateTableSql();
    }

    private String parseTypes(Field field) {
        Type genericType = field.getGenericType();
        String typeName = genericType.getTypeName();
        if (typeName.equals("long")) {
            return "Types.BIGINT";
        } else if (typeName.equals("int")) {
            return "Types.INTEGER";
        } else {
            return "Types.VARCHAR";
        }
    }

    private String parseDeserialize(Field field) {
        String deserialize = String.format("data.%s", field.getName());
        Type genericType = field.getGenericType();
        String typeName = genericType.getTypeName();
        if (typeName.equals("long") ||
        typeName.equals("int") ||
        typeName.equals("java.lang.String")) {
            return deserialize;
        }
        deserialize = String.format("Helper.toJSONString(%s)", deserialize);
        return deserialize;
    }


    private String parseSerialize(Field field) {
        String deserialize = String.format("data.%s", field.getName());
        Type genericType = field.getGenericType();
        String typeName = genericType.getTypeName();

        if (typeName.equals("long")) {
            deserialize = String.format("%s = rs.getLong(\"%s\");", deserialize, field.getName());
        } else if (typeName.equals("int")) {
            deserialize = String.format("%s = rs.getInt(\"%s\");", deserialize, field.getName());

        } else if (typeName.equals("java.lang.String")) {
            deserialize = String.format("%s = rs.getString(\"%s\");", deserialize, field.getName());
        } else {
            deserialize = String.format("%s = Helper.toObject(rs.getString(\"%s\"), new TypeReference<%s>() {});", deserialize, field.getName(), typeName);
        }
        return deserialize;
    }




    private void generateCreateTableSql() {
        StringBuilder createTableBuilder = new StringBuilder();
        createTableBuilder.append(String.format("CREATE TABLE IF NOT EXISTS `%s` (\n", this.getTableName()));
        Map<String, TableField> fieldMap = this.getFieldMap();
        Map<String, List<String>> indexMap = new HashMap<>();
        for (TableField tableField : fieldMap.values()) {
            IField iField = tableField.getiField();
            Class<? extends Serializer> serializer = iField.serializer();
            try {
                Serializer instance = serializer.newInstance();
                if (iField.isAutoIncrement()) {
                    createTableBuilder.append(String.format("\t`%s` %s NOT NULL AUTO_INCREMENT COMMENT '%s',\n", iField.fieldName(), instance.SqlType(), iField.comment()));
                } else {
                    createTableBuilder.append(String.format("\t`%s` %s NOT NULL COMMENT '%s',\n", iField.fieldName(), instance.SqlType(), iField.comment()));
                }
                if (iField.index().length() > 0) {
                    if (!indexMap.containsKey(iField.index())) {
                        indexMap.put(iField.index(), new ArrayList<>());
                    }
                    indexMap.get(iField.index()).add(iField.fieldName());
                }
            } catch (InstantiationException | IllegalAccessException e) {
                throw new RuntimeException(e);
            }
        }
        StringBuilder pkBuilder = new StringBuilder();
        for (TableField tableField : fieldMap.values()) {
            IField iField = tableField.getiField();
            if (iField.isPrimaryKey()) {
                pkBuilder.append(String.format("`%s`,", iField.fieldName()));
            }
        }
        if (pkBuilder.length() > 0) {
            pkBuilder.deleteCharAt(pkBuilder.length() - 1);
            createTableBuilder.append(String.format("\tPRIMARY KEY (%s),", pkBuilder));
        }

        for (Map.Entry<String, List<String>> stringListEntry : indexMap.entrySet()) {
            createTableBuilder.append(String.format("\tKEY `%s` (", stringListEntry.getKey()));
            for (String s : stringListEntry.getValue()) {
                createTableBuilder.append(String.format("`%s`,", s));
            }
            createTableBuilder.deleteCharAt(createTableBuilder.length() - 1);
            createTableBuilder.append("),");

        }
        createTableBuilder.deleteCharAt(createTableBuilder.length() - 1);
        createTableBuilder.append("\n");
        createTableBuilder.append(") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;");
        this.createTableSql = createTableBuilder.toString();
    }

    public String getTableName() {
        return tableName;
    }

    public Map<String, TableField> getFieldMap() {
        return fieldMap;
    }

    public <T> void parseResultSet(T t, ResultSet resultSet) {
        Class<?> cls = t.getClass();
        try {
            Set<Map.Entry<String, TableField>> entries = this.fieldMap.entrySet();
            for (Map.Entry<String, TableField> entry : entries) {
                Object object = resultSet.getObject(entry.getValue().getiField().fieldName());
                Class<? extends Serializer> serializer = entry.getValue().getiField().serializer();
                Serializer instance = serializer.newInstance();
                Field field = cls.getField(entry.getKey());
                Object deserialize = instance.deserialize(object, field);
                field.set(t, deserialize);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public String getInsertSql() {
        return insertSql;
    }

    public String getUpdateSql() {
        return updateSql;
    }

    public String getCreateTableSql() {
        return createTableSql;
    }

    public String getDbUrl() {
        return dbUrl;
    }

    public String getUpdateContent() {
        StringBuilder stringBuilder = new StringBuilder();
        stringBuilder.append(String.format("\tpublic static String updateSql = \"%s\";\n", this.getUpdateSql()));

        stringBuilder.append("\t@Override\n");
        stringBuilder.append(String.format("\tprotected boolean updateData(%s data) {\n", this.cls.getSimpleName()));
        stringBuilder.append("\t\tdata.setBeginWriteTime();\n");
        stringBuilder.append(String.format("\t\tgetDb().setConnName(\"%s\");\n", this.getDbUrl()));
        stringBuilder.append("\t\tint i = 1;\n");
        stringBuilder.append("\t\tMap<Integer, DbParameter> dbParameterMap = new HashMap<>();\n");
        for (TableField tableField : this.fieldMap.values()) {
            if (tableField.getiField().isUpdateKey()) {
                continue;
            }
            stringBuilder.append("\t\t");
            stringBuilder.append(String.format("dbParameterMap.put(i++, new DbParameter(%s, %s));", tableField.getTypes(), tableField.getDeSerializer()));
            stringBuilder.append("\n");
        }
        stringBuilder.append("\n\n");
        for (TableField tableField : this.fieldMap.values()) {
            if (!tableField.getiField().isUpdateKey()) {
                continue;
            }
            stringBuilder.append("\t\t");
            stringBuilder.append(String.format("dbParameterMap.put(i++, new DbParameter(%s, %s));", tableField.getTypes(), tableField.getDeSerializer()));
            stringBuilder.append("\n");
        }

        stringBuilder.append("\t\tif (getDb().executeNoneQuery(updateSql, dbParameterMap) > 0) {\n");
        stringBuilder.append("\t\t\treturn true;\n");
        stringBuilder.append("\t\t}\n");
        stringBuilder.append("\t\treturn false;\n");
        stringBuilder.append("\t}\n");
        return stringBuilder.toString();
    }


    public String getAddContent() {
        StringBuilder stringBuilder = new StringBuilder();
        stringBuilder.append(String.format("\tpublic static String insertSql = \"%s\";\n", this.getInsertSql()));

        stringBuilder.append("\t@Override\n");
        stringBuilder.append(String.format("\tprotected boolean addData(%s data) {\n", this.cls.getSimpleName()));
        stringBuilder.append("\t\tdata.setBeginWriteTime();\n");
        stringBuilder.append(String.format("\t\tgetDb().setConnName(\"%s\");\n", this.getDbUrl()));
        stringBuilder.append("\t\tint i = 1;\n");
        stringBuilder.append("\t\tMap<Integer, DbParameter> dbParameterMap = new HashMap<>();\n");
        for (TableField tableField : this.fieldMap.values()) {
            stringBuilder.append("\t\t");
            stringBuilder.append(String.format("dbParameterMap.put(i++, new DbParameter(%s, %s));", tableField.getTypes(), tableField.getDeSerializer()));
            stringBuilder.append("\n");
        }


        stringBuilder.append("\t\tif (getDb().executeNoneQuery(insertSql, dbParameterMap) > 0) {\n");
        stringBuilder.append("\t\t\treturn true;\n");
        stringBuilder.append("\t\t}\n");
        stringBuilder.append("\t\treturn false;\n");
        stringBuilder.append("\t}\n");
        return stringBuilder.toString();
    }

    public String getParseDataContent() {
        StringBuilder stringBuilder = new StringBuilder();
        stringBuilder.append("\t@Override\n");
        stringBuilder.append(String.format("\tprotected %s parseData(ResultSet rs) throws SQLException {\n", this.cls.getSimpleName()));
        stringBuilder.append(String.format("\t%s data = new %s();\n", this.cls.getSimpleName(), this.cls.getSimpleName()));
        for (TableField tableField : this.fieldMap.values()) {
            stringBuilder.append("\t\t");
            stringBuilder.append(tableField.getSerializer());
            stringBuilder.append("\n");
        }

        stringBuilder.append("\t\treturn data;\n");
        stringBuilder.append("\t}\n");
        return stringBuilder.toString();
    }

    public String getImplClassName() {
        return implClassName;
    }
}
